﻿using Microsoft.Practices.ESB.Adapter;
using Microsoft.Practices.ESB.Exception.Management;
using Microsoft.Practices.ESB.Itinerary;
using Microsoft.Practices.ESB.Itinerary.Services;
using Microsoft.Practices.ESB.Resolver;
using Microsoft.Practices.ESB.Transform;
using Microsoft.BizTalk.Component.Interop;
using Microsoft.BizTalk.Message.Interop;
using Microsoft.BizTalk.Streaming;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Xsl;
using System.Xml.XPath;

namespace BizTalk.ESB.Extensions.MessagingServices
{
    public class AssociatedFileRetriever : IMessagingService
    {
        private string _BizTalkSystemNamespace = @"http://schemas.microsoft.com/BizTalk/2003/system-properties";
        private string _OutMessageType = @"http://BizTalk.ESB.Extensions/Schemas#AssociatedFiles";

        public IBaseMessage Execute(IPipelineContext context, IBaseMessage msg, string resolverString, IItineraryStep step)
        {
            if (context == null)
                throw new ArgumentNullException("context");
            if (msg == null)
                throw new ArgumentNullException("msg");
            if (string.IsNullOrEmpty(resolverString))
                throw new ArgumentException("Resolver must be provided", "resolverString");
            try
            {
                ResolverInfo info = ResolverMgr.GetResolverInfo(ResolutionType.Endpoint, resolverString);
                if (!info.Success)
                    throw new RoutingException("Invalid Resolver", resolverString);

                // Resolve configuration for routing.
                Dictionary<string, string> resolverDictionary = ResolverMgr.Resolve(info, msg, context);

                var fileName = resolverDictionary["Resolver.FileName"];
                var fileExtension = resolverDictionary["Resolver.FileExtension"];
                var folderPath = resolverDictionary["Resolver.FolderPath"];
                var contentType = resolverDictionary["Resolver.ContentType"];
                var documentType = resolverDictionary["Resolver.DocumentType"];
                bool mergeFlag = Convert.ToBoolean(resolverDictionary["Resolver.Merge"]);

                if (String.IsNullOrEmpty(fileName) || String.IsNullOrEmpty(folderPath) || String.IsNullOrEmpty(contentType))
                {
                    // DEBUG
                    System.Diagnostics.Trace.WriteLine("WARNING - Returning message. File Name, Folder Path or Content Type not provided.");
                    return msg;
                }
                else
                {
                    IBaseMessage outMsg;
                    outMsg = context.GetMessageFactory().CreateMessage();
                    outMsg.AddPart("Body", context.GetMessageFactory().CreateMessagePart(), true);
                    outMsg.Context = msg.Context;

                    if (msg.BodyPart != null)
                    {
                        var originalStrm = msg.BodyPart.GetOriginalDataStream();

                        if (originalStrm != null)
                        {
                            // Build Input Xml
                            XmlDocument inputMsg = new XmlDocument();
                            inputMsg.LoadXml(this.GetMessageContent(context, msg));

                            // Resolve File Location
                            var helper = new Helpers.ItineraryServiceDataMappingConfig();
                            var filenameResolved = helper.GetMappingSourceValue(fileName, inputMsg, msg.Context);
                            var fileextensionResolved = helper.GetMappingSourceValue(fileExtension, inputMsg, msg.Context);
                            var folderpathResolved = helper.GetMappingSourceValue(folderPath, inputMsg, msg.Context);

                            // Retrieve File
                            var filecontents = RetrieveFile(folderpathResolved, filenameResolved, fileextensionResolved, contentType);

                            // Build outgoing IBaseMessage
                            if (mergeFlag)
                            {
                                filecontents = MergeMessages(inputMsg, filecontents).OuterXml;
                                contentType = "XML";
                            }

                            var vStream = new VirtualStream();
                            if (contentType.ToUpper() == "XML")
                            {
                                var outputMsg = new XmlDocument();
                                outputMsg.LoadXml(filecontents);
                                _OutMessageType = outputMsg.DocumentElement.NamespaceURI + "#" + outputMsg.DocumentElement.LocalName;
                                outputMsg.Save(vStream);
                            }
                            else
                            {
                                var bytes = new byte[filecontents.Length * sizeof(char)];
                                System.Buffer.BlockCopy(filecontents.ToCharArray(), 0, bytes, 0, bytes.Length);
                                var s = new MemoryStream(bytes);
                                vStream = new VirtualStream(s);
                            }
                            outMsg.Context.Promote("MessageType", this._BizTalkSystemNamespace, _OutMessageType);
                            vStream.Seek(0, SeekOrigin.Begin);
                            ReadOnlySeekableStream seekableStream = new ReadOnlySeekableStream(vStream);
                            outMsg.BodyPart.Data = seekableStream;
                            seekableStream.Seek(0, SeekOrigin.Begin);
                            outMsg.BodyPart.Data = seekableStream;
                            context.ResourceTracker.AddResource(seekableStream);
                        }
                    }

                    return outMsg;
                }
            }
            catch (System.Exception ex)
            {
                EventLogger.Write(MethodInfo.GetCurrentMethod(), ex);
                throw;
            }   
        }

        public string Name
        {
            get { return "BizTalk.ESB.Extensions.MessagingServices.AssociatedFileRetriever"; }
        }

        public bool ShouldAdvanceStep(IItineraryStep step, IBaseMessage msg)
        {
            return true;
        }

        public bool SupportsDisassemble
        {
            get { return false; }
        }

        #region Private Methods

        private string RetrieveFile(string FolderPath, string FileName, string FileExtension, string ContentType)
        {
            var fileLocation = FolderPath + @"\" + FileName + "." + FileExtension;
            if (System.IO.File.Exists(fileLocation))
            {
                if (ContentType.ToUpper() == "XML")
                {
                    var xdoc = new XmlDocument();
                    xdoc.Load(fileLocation);
                    return xdoc.OuterXml;
                }
                else
                {
                    return File.ReadAllText(fileLocation);
                }
            }
            else
            {
                return null;
            }
        }

        private XmlDocument MergeMessages(XmlDocument InboundMessage, string AssociatedMessage)
        {
            var sb = new StringBuilder();
            sb.Append(@"<MergedMessages xmlns='");
            sb.Append(_OutMessageType);
            sb.Append(@"' >");
            sb.Append(@"<OriginalMessage>");
            sb.Append(InboundMessage.OuterXml);
            sb.Append(@"</OriginalMessage>");
            sb.Append(@"<AssociatedMessage>");
            sb.Append(AssociatedMessage);
            sb.Append(@"</AssociatedMessage>");
            sb.Append(@"</MergedMessages>");
            var msg = new XmlDocument();
            msg.LoadXml(sb.ToString());
            return msg;
        }
        
        private String GetMessageContent(IPipelineContext pContext, IBaseMessage message)
        {
            String msgData = string.Empty;
            Stream outStream = new VirtualStream();
            StreamWriter strmWriter = new StreamWriter(outStream);

            using (StreamReader sr = new
                          StreamReader(message.BodyPart.GetOriginalDataStream()))
            {
                msgData = sr.ReadToEnd();
                strmWriter.Flush(); // flush writer to ensure writing's done.
                strmWriter.Close(); // close stream since it won't be used
                sr.Close();
                return msgData;
            }
        }

        #endregion
    }
}
